'''
基于xgboost预测准确率计算的属性选择法
1逐个（有放回）去除属性计算每个指标的影响系数
2从零开始逐个添加属性，保留带来准确率提升的指标
@author：fengye
'''
from itertools import count
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from ML import NN_model,xgb_model

# 计算使用所有属性得到准确率均值及方差
def total_mean(all_data):
    '''计算使用所有属性得到准确率均值及方差'''
    all_attribute_mean, all_attribute_square,index_importance = xgb_model(all_data)
    print('预测总体均值：')
    print(all_attribute_mean)
    print('预测总体方差：')
    print(all_attribute_square)
    print('xgb指标重要性：')
    print(index_importance)
               
    return all_attribute_mean, all_attribute_square,index_importance

# 逐一去除某一属性（有放回）后计算各个属性的影响系数，去除系数大于总方差的
def importance_ranking(all_data, all_attribute_mean, all_attribute_square):
    '''逐一去除某一属性后计算各个属性的影响系数，并去除系数大于总方差的'''
    target_counts = all_data.shape[1] - 1  # 总指标数量
    influence_coefficient = []  # 影响系数
    influence_coefficient_reduce = []  # 缩减后影响系数（按值升序排序，指标重要性递减）
    remain_lable = []  # 保留属性标签
    remove_lable = []  # 去除属性标签
    all_lable = [x for x in range(target_counts)]  # 全属性标签

    #  每次去除一个指标计算影响系数
    for target in range(target_counts):
        all_data_tmp = all_data.copy() 
        reduce_data = np.delete(all_data_tmp, target, axis=1)  # 减少一个指标后的数据
        reduce_attribute_mean, _, _ = xgb_model(reduce_data)  # 去除某属性后的准确度均值
        infl_tmp = reduce_attribute_mean - all_attribute_mean  # 每个属性影响系数
        influence_coefficient.append(infl_tmp)

    print(influence_coefficient)
    #  去除影响系数大于标准差的
    for lable in all_lable:
        if influence_coefficient[lable] > all_attribute_square:
            remove_lable.append(lable)
        else:
            remain_lable.append(lable)
            influence_coefficient_reduce.append(influence_coefficient[lable])

    #  按影响系数排序  
    count = len(influence_coefficient_reduce)
    for i in range(0, count):    # 使用冒泡排序
        for j in range(i+1, count):
            if influence_coefficient_reduce[i] > influence_coefficient_reduce[j]:
                influence_coefficient_reduce[i], influence_coefficient_reduce[
                    j] = influence_coefficient_reduce[j], influence_coefficient_reduce[i]
                remain_lable[i], remain_lable[j] = remain_lable[j], remain_lable[i]

    return influence_coefficient_reduce,influence_coefficient, remain_lable, remove_lable

# 从零开始逐个添加指标，保留是预测准确度上升的指标
def lifting_level_opt(all_data, remain_label):
    '''保留是预测准确度上升的指标'''
    acc = [0]  # 根据影响系数添加不同属性的精确度
    best_attributes = []  # 保留指标
    index_important = remain_label.copy()  #指标按照重要性排序列表，例：[15,16,1,8,12...]

    all_data_df = pd.DataFrame(data=all_data)
    data_result = all_data_df.iloc[:, -1]  # 数据标签列
   
    index_number = len(index_important)   #指标个数
    for i in range(index_number):    # 保留指标best_attributes在位置i指标添加情况
        #保留影响系数排名前n的指标
        if i <= 0:    
            best_attributes.append(index_important[i])
            data_attributes = all_data_df.iloc[:, best_attributes]  # 每次添加指标后的特征数据
            print(data_attributes[:5])
            data_add = pd.concat([data_attributes, data_result], axis=1)
            data_add = np.array(data_add)  # 转换为numpy数组放入模型中
            
            add_attribute_mean, _, _ = xgb_model(data_add)  # 添加某属性后的准确度均值
            acc.append(add_attribute_mean)
            continue

        best_reduce = False  #终止循环条件：尝试index_important[i, index_number]任何指标也没有带来准确率提升，结束循环
        for j in range(i, index_number):  # index_important[i, index_number]是否添加到保留指标
            best_attributes.append(index_important[j])
            data_attributes = all_data_df.iloc[:, best_attributes]  # 每次添加指标后的特征数据
            print(data_attributes[:5])
            data_add = pd.concat([data_attributes, data_result], axis=1)
            data_add = np.array(data_add)  
            
            add_attribute_mean, _, _ = xgb_model(data_add)  # 添加某属性后的准确度均值
            # 带来准确率提升则保留此指标，下一步确认保留指标i+1添加情况
            if add_attribute_mean > acc[-1]:
                acc.append(add_attribute_mean)
                index_important[i], index_important[j] = index_important[j], index_important[i]
                break
            # 未带来准确率提升则不添加此指标
            best_attributes.pop()

            if j == index_number: #尝试到最后指标也没带来提升，达成结束循环条件
                best_reduce = True

        if best_reduce == True:
            break

    len_attributes = len(acc)
    x = list(range(1, len_attributes))
    y = acc[1:]
    plt.plot(x, y)
    plt.ylim(0, 80)  # 限定y轴范围
    plt.xlabel('加入属性个数')
    plt.ylabel('预测准确率/%')
    plt.title('根据影响系数前向递归的预测准确率变化曲线change')
    plt.rcParams['font.sans-serif'] = ['Simhei']  # 生成图片显示中文
    plt.savefig('./result/准确率提升曲线opt.png')
    
    return best_attributes, acc[1:]



if __name__ == "__main__":
    import pandas as pd
    import numpy as np

    data_path =r'./data/trainStand.xlsx'
    data = pd.read_excel(data_path)
    data = data.fillna(data.mean())
    data = np.array(data)  # nt#也可使用data.value()
    file_name = open('./result/coding_result_change.txt','w+',encoding='utf-8')    #保存运行结果

    # 总体均值与方差
    all_attribute_mean, all_attribute_square, index_importance= total_mean(data.copy())  
    # 计算属性影响系数，按影响系数保留和去除属性
    influence_coefficient,influence_coefficient_all, remain_lable, remove_lable = importance_ranking(
        data.copy(), all_attribute_mean, all_attribute_square)
    print('计算影响系数保留属性：')
    print(remain_lable)
    print('保留属性的影响系数：')
    print(influence_coefficient)   
    # 计算属性的提升系数
    best_attributes, acc = lifting_level_opt(data.copy(), remain_lable.copy())
    print('最佳属性组合：')
    print(best_attributes)

    #保存运行结果
    file_name.write('总体均值与方差：\n')
    file_name.write(str(all_attribute_mean) + '  ' + str(all_attribute_square))  
    file_name.write('\n-------------------------------\n')
    file_name.write('指标按重要性排序：\n')
    file_name.write(str(remain_lable) + '\n')
    file_name.write('指标重要性：\n')
    file_name.write(str(influence_coefficient))
    file_name.write('\n------------------------------\n')
    file_name.write('最佳属性组合：\n')
    file_name.write(str(best_attributes) + '\n')
    file_name.write('最佳属性组合对应的准确度变化：\n')
    file_name.write(str(acc) + '\n')
    file_name.close()